--[[

    Open MceIRServer Plugin.

--]]

local Super = require('transport.TransactionBased')
local Transaction = require('transport.Transaction')
local prepare = require('transport.PrepareSettings')
local constants = require('transport.constants')
local table = require('table')
local print = print
local math = require('math')
local gir = gir
local string = require('string')
local pairs = pairs
local DM = require('transport.DM')
local DeviceManager = DeviceManager
local tostring = tostring
local tonumber = tonumber

module("transport.devices.mceIr")


local DefaultSettings = prepare(
    -- first base settigs.
    {
        Transport = {
            Post = {
                NoResponseTimeout = 2000,
            },
        },
        Parser = {
            Type = constants.parser.TERMINATED,
            Terminator = '\r\n',
            Timeout = 1000,
        },
        ConnectionMonitor = {
            Interval = 10000,
            RetryInterval = 10000,
        },
        MaxQueuedItems = 10,
    },
    -- overrides for serial transport
    {
    }
)


local TX = Transaction:Subclass ({

    Send = {
        Prefix = '',
        Suffix = '\r\n',
        CommandSeparator = ' ',
        ParameterSeparator = ' ',
    },

    Response = {
        Params = {
            [1] = {
                Parse = function(self, parameter) return parameter end,
            },
            [2] = {
                Parse = function(self, parameter)  return parameter end,
            },
        },
    },

    IsError = function (self, data)
        if string.find(data,'^e.*') then
            return true
        end
        return false
    end,

    AnalyseResponse = function(self, data)
        if self:IsError(data) then
            return nil,nil, self.ResponseCodes.Error
        end
        local _, _, cmd, a,b = string.find(data, '^(%w)')

        return cmd, {data, a,b}
    end,

    SetResults = function(self, obj, a,b)
    end,

    OnNoResponse = function(self, obj)
        obj:Log(5, "No Response Received", self.Send.Command)
    end,
})

local GetVersion = TX:Subclass ( {

    Send = {
        Command = "i",
    },

    Response = {
        Command = 'i',
    },

    AnalyseResponse = function(self, data)
        if self:IsError(data) then
            return nil,nil, self.ResponseCodes.Error
        end
        local _, _, cmd, name,ver = string.find(data, '^(%w) (%w+) ([%w%.]+)')

        return cmd, {name,ver}
    end,

    SetResults = function(self, obj, name,ver)
        obj:_SetVersion(name,ver)
    end,
})

local GetStatus = TX:Subclass ( {

    Send = {
        Command = "s",
    },

    Response = {
        Command = 's',
    },

    AnalyseResponse = function(self, data)
        if self:IsError(data) then
            return nil,nil, self.ResponseCodes.Error
        end
        local _, _, cmd, status = string.find(data, '^(%w) ([%w_]+)')

        return cmd, {status}
    end,

    SetResults = function(self, obj, status)
        obj:_SetStatus(status)
    end,
})


local IRListener = TX:Subclass ( {

    ResponseOnly = true,
    Persist = true,

    Response = {
        Command = 'R',
    },

    AnalyseResponse = function(self, data)
        if self:IsError(data) then
            return nil,nil, self.ResponseCodes.Error
        end
        local _, _, cmd, protocol, keyval, rep, keyname = string.find(data, '^(%w) (%w+) (%w+) (%w+) (.+)')
        local eventstring
        if protocol and keyval and keyname then
             eventstring = protocol.." " .. keyval .. " " .. keyname
        else
            eventstring = ''
        end

        return cmd, {eventstring,rep}
    end,

    SetResults = function(self, obj, eventstring, rep)
        if ( tonumber(rep) > 0 ) then
            gir.TriggerEventEx(eventstring,600, 4)
        else
            gir.TriggerEventEx(eventstring,600, 1)
        end
    end,
})


local SendIR = TX:Subclass ( {

    Send = {
        Command = "t",
        Params = {
            [1] = {
                Type = 'number',
                Description = 'hardware id',
                Optional = false,
            },
            [2] = {
                Type = 'number',
                Description = 'blaster bitmask (1,2 or 3)',
                Optional = false,
            },
            [3] = {
                Type = 'number',
                Description = 'repeat count',
                Optional = false,
            },
            [4] = {
                Type = 'string',
                Description = 'CCF',
                Optional = false,
            },
        },
    },

    Response = {
        Command = 't',
    },

    AnalyseResponse = function(self, data)
        if self:IsError(data) then
            return nil,nil, self.ResponseCodes.Error
        end
        local _, _, cmd, status = string.find(data, '^(%w) ([%w_]+)')

        return cmd, {status}
    end,

    SetResults = function(self, obj, status)
        -- obj:_SetStatus(status)
        if status ~= "OK" then
            print("IR SEND Error: ", status)
        end
    end,
})



local obj = Super:Subclass ( {

    Name = 'Open MceIrService Connector',
    Description = 'Open MceIrService Conenctor. This driver supports Windows MCE remotes.',
    GUIDefaults = {

        allowedtransports = {
            [constants.transport.GIP] = true,
            [constants.transport.SERIAL] = false,
        },
        defaulttransport = constants.transport.GIP,
        hostname = "localhost",
        port = 12643,
    },


    Initialize = function(self )
        Super.Initialize(self)
        self.Devices = {}
        self.Handlers = {}
    end,

    GetSettings = function ( self )
        return table.copy(DefaultSettings[self.TransportType])
    end,

    OnConnectFailed = function ( self )
        print("MceIr Connection Failed")
    end,

    OnConnected = function ( self )
        self:_IRListener()
        self:_GetVersion()
        self:_GetStatus()

    end,

    OnDisconnected = function(self)
    end,

    OnConnectionCheck = function (self)
        self:_GetStatus ()
    end,

    _GetVersion = function ( self )
        self.TransactionManager:QueueCommandClass( GetVersion )
    end,

    _GetStatus = function ( self )
        self.TransactionManager:QueueCommandClass( GetStatus )
    end,

    _IRListener = function ( self )
        self.TransactionManager:QueueCommandClass( IRListener )
    end,

    _SendIR = function ( self, hardwareId, blasters, repeatCnt, ccf )
        self.TransactionManager:QueueCommandClass( SendIR, hardwareId, blasters, repeatCnt, ccf )
    end,

    _SetStatus = function ( self, status )


        if ( self.lastStatus and self.lastStatus == status ) then
            return
        end

        self.lastStatus = status

        if ( tonumber(status) > 0 ) then

			if ( tonumber( status ) > 1 ) then
				self:SetStatus(self.Statuses.Ok, "Connected & <color=#00aa00>" .. status .. " MceIr devices found</color>, server: " .. self.version)
			else
				self:SetStatus(self.Statuses.Ok, "Connected & <color=#00aa00>1 MceIr device found</color>, server: " .. self.version)
			end

            for i=0,tonumber(status)-1 do
                self:AddIR(i, 1)
                self:AddIR(i, 2)
                self:AddIR(i, 3)
            end

        else
            self:SetStatus(self.Statuses.Ok, "Connected but <color=#aa000>no hardware found</color>, server: " .. self.version)
        end

    end,

    _SetVersion = function (self, name, version )
        self.version = name .. " " .. version
        self:SetStatus(self.Statuses.Ok, "Connected server: " .. self.version)
    end,



    --[[ DEVICE MANAGER
    --]]
    -- Removes the devices if the GC goes missing.
    _ClearDevices = function ( self )
        for port, device in pairs(self.Devices) do
            self:Event (DM.Events.DeviceDelete, device)
        end
        self.Devices = {}
        self.Handlers = {}
    end,

    -- GetDevices is called during startup and shutdown of this instance
    -- to add or remove any devices we might have at that point.
    -- This plugin does not have any fixed devices, it queries for them
    -- at start up.
    GetDevices = function ( self )
        local dev = {}
        for tid, device in pairs(self.Devices) do
            dev[ device:GetID() ] = device
        end
        return dev
    end,

    -- This is called by the Transport Manager Provider if from Girder the state of
    -- a control changes, we need to figure out what control is being updated and
    -- update the hardware to reflect this change. Note that once the hardware is update
    -- we have to send an event back into the DM system changing the state of the control.
    -- think of this as a 'request to change state' and the state will be changed by us.
    DeviceCommand = function(self, Command, Device, Control, newvalue, port)
        local handler = self.Handlers[port]
        if not handler then
            return
        end
        if handler.DeviceCommand then
            handler.DeviceCommand(self, handler, Command, Device, Control, newvalue, port)
        end
    end,


    -- This function is called when the state of a Sensor or Relay changes
    -- this event is then handed off to the handler table that belongs to that device.
    UpdateState = function ( self, port, state )
        local handler = self.Handlers[port]
        if not handler then
            return
        end
        if handler.DeviceChange then
            handler.DeviceChange(self, handler, state, port)
        end
    end,

    -- Adds an IR output device to Girder.
    AddIR = function ( self, hardwareId,port )


        local myid = 'dev' .. hardwareId .. "-" .. port
        if self.Devices[myid] then
            return
        end

        local deviceproperties = {
            ID = DM.makeid(self, myid),
            Location = 'MceIr',
            Name = 'IR '.. hardwareId .. ' ' .. port ,
            Description = 'MceIr blaster',
            Status = DeviceManager.Devices.Statuses.Ok,
            Provider = self:GetComponent().Provider,
        }

        local device =  (DeviceManager.Devices.Classes.InfraRedTransmitterCCF:New  (deviceproperties))
        local handler = {

            Device = device,

            -- DeviceCommand = initiated from Girder / Computer -> Means update hardware.
            DeviceCommand = function ( self, handler, Command, Device, Control, newvalue, Xport)
                if Control:GetID() ~= 'SendCCF' then
                    return
                end
                local repeatcount =  (Device:GetControl ('Repeat'):GetValue ())

                if not self:_SendIR(tonumber(hardwareId), tonumber(port), tonumber(repeatcount), newvalue) then
                    self:Event(DM.Events.DeviceState, handler.Device, 'SendCCF', 'Error')
                end
            end,
            -- DeviceChange = initiated from device it self -> Means update Girder's Device Manager.
            DeviceChange = function ( self, handler, newvalue, port )
                self:Event(DM.Events.DeviceState, handler.Device, 'SendCCF', newvalue)
            end,

        }
        self.Devices[myid] = device
        self.Handlers[myid] = handler
        self:Event (DM.Events.DeviceAdd, device)
        self:Event(DM.Events.DeviceState, handler.Device, 'SendCCF', 'Ok')
    end,
} )


Name = obj.Name
Description = obj.Description
GUIDefaults = obj.GUIDefaults
function New (self,settings)
    return obj:New(settings)
end

